/**
 * 
 */
package org.statewalker.tokenizer.stream;

/**
 * @author kotelnikov
 */
public abstract class AbstractCharStream implements ICharStream {

    private static final int DELTA = 2;

    private char[] fBuf = new char[10];

    private Pointer fEnd;

    private Pointer fFirstMark;

    private int fMarkCounter;

    private Pointer fPointer = null;

    private Pointer fTop = Pointer.START;

    /**
     * 
     */
    private void checkPointer() {
        if (fPointer == null)
            incPos();
    }

    public char getChar() {
        checkPointer();
        return (char) (fBuf[fPointer.pos % fBuf.length] & 0xFFFF);
    }

    public Pointer getPointer() {
        checkPointer();
        return fPointer;
    }

    /**
     * @param p
     * @param newLine
     * @return
     */
    private Pointer incPointer(Pointer p) {
        if (p == null)
            return Pointer.START;
        char prev = p.pos > 0 ? fBuf[(p.pos - 1) % fBuf.length] : '\0';
        boolean newLine = false;
        if (prev == '\r') {
            char current = fBuf[p.pos % fBuf.length];
            newLine = (current != '\n');
        } else {
            newLine = prev == '\n';
        }
        p = p.inc(newLine);
        return p;
    }

    public boolean incPos() {
        fPointer = incPointer(fPointer);
        if (fPointer.pos >= fTop.pos - 1) {
            if (fEnd != null) {
                fPointer = fEnd;
                return false;
            }
            int val = readNext();
            if (fMarkCounter > 0
                && fTop.pos - fFirstMark.pos + DELTA >= fBuf.length) {
                int len = fBuf.length * 3 / 2;
                char[] buf = new char[len];
                for (int pos = fFirstMark.pos; pos <= fTop.pos + DELTA; pos++) {
                    buf[pos % buf.length] = fBuf[pos % fBuf.length];
                }
                fBuf = buf;
            }
            char ch;
            if (val >= 0) {
                ch = (char) (val & 0xFFFF);
            } else {
                fEnd = fTop;
                ch = '\0';
            }
            fBuf[fTop.pos % fBuf.length] = ch;
            fTop = incPointer(fTop);
        }
        if (fMarkCounter == 0)
            fFirstMark = fPointer;
        return true;
    }

    public IMarker markPosition() {
        checkPointer();
        if (fMarkCounter == 0)
            fFirstMark = fPointer;

        fMarkCounter++;
        return new IMarker() {

            Pointer fMarker = fPointer;

            public void close(boolean reset) {
                if (reset) {
                    fPointer = fMarker;
                }
                fMarkCounter--;
                if (fMarkCounter == 0)
                    fFirstMark = fPointer;
            }

            public Pointer getPointer() {
                return fMarker;
            }

            public String getSubstring(int len) {
                StringBuffer buf = new StringBuffer();
                for (int i = fMarker.pos; i < fPointer.pos; i++) {
                    char ch = fBuf[i % fBuf.length];
                    buf.append(ch);
                }
                return buf.toString();
            }

            @Override
            public String toString() {
                int len = fPointer.pos - fMarker.pos;
                return "Marker(" + fMarker + ":'" + getSubstring(len) + "')";
            }
        };
    }

    protected abstract int readNext();

}
